package io.openaev.rest;

import static io.openaev.rest.vulnerability.VulnerabilityApi.VULNERABILITY_API;
import static io.openaev.utils.JsonUtils.asJsonString;
import static io.openaev.utils.fixtures.VulnerabilityFixture.*;
import static io.openaev.utils.fixtures.VulnerabilityFixture.CVE_2025_5678;
import static io.openaev.utils.fixtures.VulnerabilityInputFixture.createDefaultVulnerabilityInput;
import static java.time.Instant.now;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.openaev.IntegrationTest;
import io.openaev.database.model.Collector;
import io.openaev.database.model.Vulnerability;
import io.openaev.database.repository.VulnerabilityRepository;
import io.openaev.rest.vulnerability.form.VulnerabilityBulkInsertInput;
import io.openaev.rest.vulnerability.form.VulnerabilityCreateInput;
import io.openaev.rest.vulnerability.form.VulnerabilityUpdateInput;
import io.openaev.utils.fixtures.CollectorFixture;
import io.openaev.utils.fixtures.composers.CollectorComposer;
import io.openaev.utils.fixtures.composers.VulnerabilityComposer;
import io.openaev.utils.mockUser.WithMockUser;
import io.openaev.utils.pagination.SearchPaginationInput;
import jakarta.annotation.Resource;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.util.List;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

@TestInstance(PER_CLASS)
@Transactional
@WithMockUser(isAdmin = true)
@DisplayName("CVE API Integration Tests")
class VulnerabilityApiTest extends IntegrationTest {

  @Resource protected ObjectMapper mapper;
  @Autowired private MockMvc mvc;
  private Collector collector;

  @Autowired private VulnerabilityComposer vulnerabilityComposer;
  @Autowired private CollectorComposer collectorComposer;
  @Autowired private VulnerabilityRepository vulnerabilityRepository;

  @BeforeAll
  void init() {
    collector =
        collectorComposer
            .forCollector(CollectorFixture.createDefaultCollector("CS"))
            .persist()
            .get();
  }

  @BeforeEach
  void setUp() {
    vulnerabilityComposer.reset();
  }

  @Nested
  @DisplayName("When working with vulnerabilities")
  @WithMockUser(isAdmin = true)
  class WhenWorkingWithVulnerabilities {

    @Test
    @DisplayName("Should create a new vulnerability successfully")
    void shouldCreateNewVulnerability() throws Exception {
      VulnerabilityCreateInput input = new VulnerabilityCreateInput();
      input.setExternalId("CVE-2025-1234");
      input.setCvssV31(new BigDecimal("5.2"));
      input.setDescription("Test summary for vulnerability creation");

      String response =
          mvc.perform(
                  post(VULNERABILITY_API)
                      .contentType(MediaType.APPLICATION_JSON)
                      .content(asJsonString(input)))
              .andExpect(status().isOk())
              .andReturn()
              .getResponse()
              .getContentAsString();

      assertThatJson(response).node("vulnerability_external_id").isEqualTo("CVE-2025-1234");
    }

    @Test
    @DisplayName("Should fetch a Vulnerability by ID")
    void shouldFetchCveById() throws Exception {
      Vulnerability vulnerability = new Vulnerability();
      vulnerability.setExternalId(CVE_2025_5678);
      vulnerability.setCvssV31(new BigDecimal("8.9"));
      vulnerability.setDescription("Test Vulnerability");

      vulnerabilityComposer.forVulnerability(vulnerability).persist();

      String response =
          mvc.perform(get(VULNERABILITY_API + "/" + vulnerability.getId()))
              .andExpect(status().isOk())
              .andReturn()
              .getResponse()
              .getContentAsString();

      assertThatJson(response).node("vulnerability_external_id").isEqualTo(CVE_2025_5678);
    }

    @Test
    @DisplayName("Should update an existing Vulnerability")
    void shouldUpdateVulnerability() throws Exception {
      Vulnerability vulnerability = new Vulnerability();
      vulnerability.setExternalId("CVE-2025-5679");
      vulnerability.setCvssV31(new BigDecimal("4.5"));
      vulnerability.setDescription("Old description");
      vulnerabilityComposer.forVulnerability(vulnerability).persist();

      VulnerabilityUpdateInput updateInput = new VulnerabilityUpdateInput();
      updateInput.setDescription("Updated Summary");

      mvc.perform(
              put(VULNERABILITY_API + "/" + vulnerability.getId())
                  .contentType(MediaType.APPLICATION_JSON)
                  .content(asJsonString(updateInput)))
          .andExpect(status().isOk())
          .andReturn()
          .getResponse()
          .getContentAsString();

      Assertions.assertTrue(
          updateInput
              .getDescription()
              .equals(
                  vulnerabilityRepository
                      .findById(vulnerability.getId())
                      .map(cve1 -> cve1.getDescription())
                      .get()));
    }

    @Test
    @DisplayName("Should bulk insert multiple Vulnerabilities")
    void shouldBulkInsertVulnerabilities() throws Exception {
      // -- PREPARE -
      VulnerabilityCreateInput input = createDefaultVulnerabilityInput();
      VulnerabilityBulkInsertInput inputs = new VulnerabilityBulkInsertInput();
      inputs.setSourceIdentifier(collector.getId());
      inputs.setLastModifiedDateFetched(now());
      inputs.setLastIndex(1234);
      inputs.setInitialDatasetCompleted(false);
      inputs.setVulnerabilities(List.of(input));

      // -- EXECUTE --

      mvc.perform(
              post(VULNERABILITY_API + "/bulk")
                  .content(asJsonString(inputs))
                  .contentType(MediaType.APPLICATION_JSON)
                  .accept(MediaType.APPLICATION_JSON))
          .andExpect(status().isOk())
          .andReturn()
          .getResponse()
          .getContentAsString();

      // -- ASSERT --
      Assertions.assertTrue(
          vulnerabilityRepository.findByExternalId(VULNERABILITY_EXTERNAL_ID).isPresent());
    }

    @Test
    @DisplayName("Should delete a Vulnerability")
    void shouldDeleteVulnerability() throws Exception {
      Vulnerability vulnerability = new Vulnerability();
      vulnerability.setExternalId("CVE-2025-5679");
      vulnerability.setCvssV31(new BigDecimal("7.5"));
      vulnerability.setDescription("To be deleted");
      vulnerabilityComposer.forVulnerability(vulnerability).persist();

      mvc.perform(delete(VULNERABILITY_API + "/" + vulnerability.getExternalId()))
          .andExpect(status().isOk());

      Assertions.assertFalse(
          vulnerabilityRepository.findById(vulnerability.getExternalId()).isPresent());
    }

    @Test
    @DisplayName("Should return Vulnerabilities on search")
    void shouldReturnVulnerabilitiesOnSearch() throws Exception {
      Vulnerability vulnerability = new Vulnerability();
      vulnerability.setExternalId("CVE-2024-5679");
      vulnerability.setCvssV31(new BigDecimal("4.5"));
      vulnerability.setDescription("Vulnerability 1");
      vulnerabilityComposer.forVulnerability(vulnerability).persist();

      Vulnerability cve1 = new Vulnerability();
      cve1.setExternalId("CVE-2025-5671");
      cve1.setCvssV31(new BigDecimal("1.8"));
      cve1.setDescription("Vulnerability 2");
      vulnerabilityComposer.forVulnerability(cve1).persist();

      SearchPaginationInput input = new SearchPaginationInput();
      input.setSize(10);
      input.setPage(0);

      String response =
          mvc.perform(
                  post(VULNERABILITY_API + "/search")
                      .content(asJsonString(input))
                      .contentType(MediaType.APPLICATION_JSON)
                      .accept(MediaType.APPLICATION_JSON))
              .andExpect(status().isOk())
              .andReturn()
              .getResponse()
              .getContentAsString();

      assertThatJson(response)
          .inPath("content[*].vulnerability_external_id")
          .isArray()
          .contains("CVE-2024-5679", "CVE-2025-5671");
    }
  }
}
